#include "occt_widget.h"

#include <Graphic3d_GraphicDriver.hxx>
#include <V3d_Viewer.hxx>
#include <V3d_View.hxx>
#include <AIS_InteractiveContext.hxx>
#include <TDocStd_Document.hxx>
#include <XCAFDoc_ShapeTool.hxx>
#include <XCAFApp_Application.hxx>
#include <STEPCAFControl_Reader.hxx>
#include <Aspect_DisplayConnection.hxx>
#include <OpenGl_GraphicDriver.hxx>
#include <WNT_Window.hxx>
#include <STEPCAFControl_Controller.hxx>
#include <XCAFDoc_DocumentTool.hxx>
#include <AIS_Shape.hxx>
#include <AIS_PointCloud.hxx>
#include <TopoDS_Face.hxx>
#include <TDataStd_Name.hxx>
#include <AIS_TextLabel.hxx>
#include <Prs3d_TextAspect.hxx>
#include <TopoDS.hxx>

#include <QMouseEvent>
#include <QtConcurrent>

#include "miniply.h"
#include "utils.h"
#include "pcl_module.h"

static Handle(Graphic3d_GraphicDriver) &GetGraphicDriver()
{
    static Handle(Graphic3d_GraphicDriver) driver;
    return driver;
}

class OcctWidgetPrivate
{
public:
    OcctWidgetPrivate(QWidget *parent){}
    ~OcctWidgetPrivate(){}

public:
    Handle_V3d_Viewer m_pViewer;
    Handle_V3d_View m_pView;
    Handle_AIS_InteractiveContext m_pContext;
    Handle_TDocStd_Document m_pDoc;
    Handle_XCAFDoc_ShapeTool m_pTool;
    Handle_XCAFApp_Application m_pApp;
    STEPCAFControl_Reader m_reader;
    QString m_stepFileName;
    int m_lastX = 0;
    int m_lastY = 0;

    QVector<Handle_AIS_PointCloud> m_targetCloud;
    QVector<QVector<Handle_AIS_PointCloud>> m_measureCloud;
};

OcctWidget::OcctWidget(QWidget *_parent)
    : QWidget(_parent)
{
    d.reset(new OcctWidgetPrivate(this));
}

OcctWidget::~OcctWidget()
{
}

void OcctWidget::initOcctWidget()
{
    this->setAttribute(Qt::WA_NoSystemBackground);
    this->setAttribute(Qt::WA_PaintOnScreen);
    this->setMouseTracking(true);

    Handle(Aspect_DisplayConnection) connection = new Aspect_DisplayConnection();

    if(GetGraphicDriver().IsNull())
        GetGraphicDriver() = new OpenGl_GraphicDriver(connection);

    WId myHandle = (WId)(this->winId());
    Handle(WNT_Window) wind = new WNT_Window((Aspect_Handle)myHandle);

    d->m_pViewer = new V3d_Viewer(GetGraphicDriver());
    d->m_pView = d->m_pViewer->CreateView();
    d->m_pView->SetWindow(wind);
    if(!wind->IsMapped()) wind->Map();

    d->m_pContext = new AIS_InteractiveContext(d->m_pViewer);

    d->m_pViewer->SetDefaultLights();
    d->m_pViewer->SetLightOn();

    d->m_pView->SetBgGradientColors(Quantity_NOC_LIGHTBLUE, Quantity_NOC_STEELBLUE);
    d->m_pView->MustBeResized();
    d->m_pView->TriedronDisplay(Aspect_TOTP_LEFT_LOWER, Quantity_NOC_GOLD, 0.08, V3d_ZBUFFER);

    d->m_pContext->SetDisplayMode(AIS_Shaded, Standard_True);

    STEPCAFControl_Controller::Init();
    this->d->m_reader.SetColorMode(true);
    this->d->m_reader.SetNameMode(true);
    this->d->m_reader.SetColorMode(true);
    this->d->m_reader.SetNameMode(true);
    this->d->m_reader.SetLayerMode(true);
    this->d->m_reader.SetPropsMode(true);
    this->d->m_reader.SetGDTMode(true);
    this->d->m_reader.SetMatMode(true);
    this->d->m_reader.SetViewMode(true);
}

QPaintEngine* OcctWidget::paintEngine() const
{
    return nullptr;
}

void OcctWidget::paintEvent(QPaintEvent *event)
{
    Q_UNUSED(event);
    if(!d->m_pView.IsNull()) d->m_pView->Redraw();
}

void OcctWidget::resizeEvent(QResizeEvent *event)
{
    Q_UNUSED(event);
    if(!d->m_pView.IsNull()) d->m_pView->MustBeResized();
}

void OcctWidget::mousePressEvent(QMouseEvent *event)
{
    if(!d->m_pView.IsNull()) {
        QPoint curPos = event->pos();
        switch(event->button()) {
        case Qt::LeftButton:
            d->m_pContext->SelectDetected(AIS_SelectionScheme_Replace);
            d->m_pContext->InitSelected();
            while (d->m_pContext->MoreSelected()) {
                if (d->m_pContext->HasSelectedShape()) {
                    TopoDS_Shape selectedShape = d->m_pContext->SelectedShape();
                    if (selectedShape.ShapeType() == TopAbs_FACE) {
                        TopoDS_Face selectedFace = TopoDS::Face(selectedShape);
                        Handle(AIS_InteractiveObject) obj = d->m_pContext->SelectedInteractive();
                        d->m_pContext->Redisplay(obj, Standard_True);
                        emit this->faceSelected(1, true);
                    }
                }
                d->m_pContext->NextSelected();
            }
            break;
        case Qt::MiddleButton:
            d->m_pView->StartRotation(curPos.x(), curPos.y());
            break;
        case Qt::RightButton:
            d->m_lastX = curPos.x();
            d->m_lastY = curPos.y();
            break;
        default:
            break;
        }
    }
}

void OcctWidget::mouseReleaseEvent(QMouseEvent *event)
{
    if(!d->m_pView.IsNull()) {
        //QPoint curPos = event->position().toPoint();
        switch(event->button()) {
        case Qt::LeftButton:
            break;
        case Qt::MiddleButton:
            break;
        case Qt::RightButton: {
            //             QMenu menu;
            //             QAction *act = menu.addAction("E");
            //             menu.exec(QCursor::pos());
            break;
        }
        default:
            break;
        }
    }
}

void OcctWidget::mouseMoveEvent(QMouseEvent *event)
{
    if(!d->m_pView.IsNull() && !d->m_pContext.IsNull()) {
        QPoint curPos = event->pos();
        if(event->buttons() & Qt::LeftButton & Qt::LeftButton) {

        } else if(event->buttons() & Qt::RightButton & Qt::RightButton) {
            d->m_pView->Pan(curPos.x() - d->m_lastX, d->m_lastY - curPos.y());
            d->m_lastX = curPos.x();
            d->m_lastY = curPos.y();
        } else if(event->buttons() & Qt::MiddleButton & Qt::MiddleButton) {
            d->m_pView->Rotation(curPos.x(), curPos.y());
        } else {
        }
        d->m_pContext->MoveTo(curPos.x(), curPos.y(), d->m_pView, Standard_True);
    }
}

void OcctWidget::wheelEvent(QWheelEvent *event)
{
    if(!d->m_pView.IsNull()) {
        QPoint curPos = event->pos();
        Standard_Integer factor = 16;
        Standard_Integer aX = curPos.x();
        Standard_Integer aY = curPos.y();

        if(event->angleDelta().y() > 0) {
            aX += factor;
            aY += factor;
        } else {
            aX -= factor;
            aY -= factor;
        }
        d->m_pView->Zoom(curPos.x(), curPos.y(), aX, aY);
    }
}

void OcctWidget::readSTEP(const QString &fileName)
{
    //QtConcurrent::run([=](){
    this->d->m_stepFileName = fileName;
    this->d->m_reader.ReadFile(fileName.toStdString().c_str());
    this->d->m_pApp = XCAFApp_Application::GetApplication();
    this->d->m_pApp->NewDocument("MDTV-XCAF", this->d->m_pDoc);
    bool isOK = this->d->m_reader.Transfer(this->d->m_pDoc);
    processSTEP();
    //});
}

void OcctWidget::processSTEP()
{
    TDF_Label mainLabel = this->d->m_pDoc->Main();
    this->d->m_pTool = XCAFDoc_DocumentTool::ShapeTool(mainLabel);
    TDF_LabelSequence seq;
    this->d->m_pTool->GetFreeShapes(seq);
    TDF_Label topLabel = seq.Value(1);
    m_entity.tdfLabel = topLabel;
    TopoDS_Shape topShape = this->d->m_pTool->GetShape(topLabel);
    m_entity.bndBox = Utils::BndUtils::AisObject_boundingBox(new AIS_Shape(topShape));
    TDF_LabelSequence components;
    if(this->d->m_pTool->GetComponents(topLabel, components)) {
        for(auto index = 1; index <= components.Length(); ++index) {
            TDF_Label childLabel = components.Value(index);
            TopoDS_Shape childShape = this->d->m_pTool->GetShape(childLabel);
            Handle(AIS_Shape) childAISShape = new AIS_Shape(childShape);
            childAISShape->SetColor((Quantity_NameOfColor)(QRandomGenerator::global()->bounded(10, 500)));
            d->m_pContext->Display(childAISShape, Standard_True);
            Utils::GraphicsEntity::Object object;
            object.aisShape = childAISShape;
            object.tdfLabel = childLabel;
            object.bndBox = Utils::BndUtils::AisObject_boundingBox(childAISShape);
            Utils::BndUtils::add(&m_entity.bndBox, object.bndBox);
            object.trsfOriginal = Utils::XCaf::shapeAbsoluteLocation(m_entity.tdfLabel, object.tdfLabel);
            m_entity.vecObject.push_back(object);
            d->m_pContext->Activate(childAISShape, AIS_Shape::SelectionMode(TopAbs_FACE));
        }
    } else {
        d->m_pContext->Display(new AIS_Shape(this->d->m_pTool->GetShape(topLabel)), Standard_True);
    }
    d->m_pView->FitAll();
    d->m_pView->ZFitAll();
    d->m_pView->MustBeResized();
}

void OcctWidget::unselectFace(int faceID)
{

}

void OcctWidget::setSTEPSlide(int value)
{
    const gp_Pnt entityCenter = Utils::BndBoxCoords::get(m_entity.bndBox).center();
    for(Utils::GraphicsEntity::Object& item : m_entity.vecObject) {
        gp_Trsf oriTrsf;
        const gp_Vec vecDirection(entityCenter, Utils::BndBoxCoords::get(item.bndBox).center());
        oriTrsf.SetTranslation(float(value) / 100.0f * 2 * vecDirection);
        d->m_pContext->SetLocation(item.aisShape, oriTrsf);
    }
    d->m_pContext->UpdateCurrentViewer();
}

void OcctWidget::readPly(int index, QString path)
{
    miniply::PLYReader reader(path.toStdString().c_str());
    if (!reader.valid()) {
        return;
    }
    uint32_t propIdxs[3];
    uint32_t numVerts;
    float *pos = nullptr;
    Handle(AIS_PointCloud) aPointCloud = new AIS_PointCloud();
    Handle(Graphic3d_ArrayOfPoints) aPoints = new Graphic3d_ArrayOfPoints(reader.element()->count);
    pcl::PointCloud<pcl::PointXYZRGB>::Ptr importedCloud(new pcl::PointCloud<pcl::PointXYZRGB>);
    if(reader.has_element()) {
        reader.load_element();
        reader.find_pos(propIdxs);
        numVerts = reader.num_rows();
        pos = new float[numVerts * 3];
        reader.extract_properties(propIdxs, 3, miniply::PLYPropertyType::Float, pos);
        for(uint32_t i = 0; i < numVerts; ++i) {
            aPoints->AddVertex(gp_Pnt(pos[i * 3], pos[i * 3 + 1], pos[i * 3 + 2]));
            pcl::PointXYZRGB point;
            point.x = pos[i * 3];
            point.y = pos[i * 3 + 1];
            point.z = pos[i * 3 + 2];
            importedCloud->push_back(point);
        }

    }
    aPointCloud->SetPoints(aPoints);
    aPointCloud->SetColor(Quantity_NOC_RED);
    d->m_pContext->SetAutomaticHilight(Standard_True);
    d->m_pContext->Display(aPointCloud, Standard_True);
    if(d->m_targetCloud.size() <= index) {
        d->m_targetCloud.push_back(aPointCloud);
        PCLModule::m_targetCloud.push_back(importedCloud);
    } else {
        d->m_pContext->Erase(d->m_targetCloud.at(index), Standard_True);
        d->m_targetCloud.replace(index, aPointCloud);
        PCLModule::m_targetCloud.replace(index, importedCloud);
    }

    //    Handle(AIS_TextLabel) aTextLabel = new AIS_TextLabel();
    //    aTextLabel->SetText("Hello, Open CASCADE!");
    //    aTextLabel->SetPosition(Utils::BndBoxCoords::get(object.bndBox).center());
    //    aTextLabel->SetColor(Quantity_NOC_RED);
    //    aTextLabel->SetFont("Arial");
    //    aTextLabel->SetHeight(20.0);
    //    aTextLabel->SetHJustification(Graphic3d_HTA_LEFT);
    //    aTextLabel->SetVJustification(Graphic3d_VTA_BOTTOM);
    //    d->m_pContext->Display(aTextLabel, Standard_True);

    d->m_pView->FitAll();
    d->m_pView->ZFitAll();
    d->m_pView->MustBeResized();
}

void OcctWidget::readPlys(int index, QString path)
{
    QStringList pathList = path.split(";");
    int i = 0;
    QVector<Handle_AIS_PointCloud> tempVec;
    QVector<pcl::PointCloud<pcl::PointXYZRGB>::Ptr> tempVec2;
    for(QString &item : pathList) {
        if(item.isEmpty()) continue;
        miniply::PLYReader reader(item.toStdString().c_str());
        if (!reader.valid()) {
            return;
        }
        uint32_t propIdxs[3];
        uint32_t numVerts;
        float *pos = nullptr;
        Handle(AIS_PointCloud) aPointCloud = new AIS_PointCloud();
        Handle(Graphic3d_ArrayOfPoints) aPoints = new Graphic3d_ArrayOfPoints(reader.element()->count);

        if(reader.has_element()) {
            reader.load_element();
            reader.find_pos(propIdxs);
            numVerts = reader.num_rows();
            pos = new float[numVerts * 3];
            reader.extract_properties(propIdxs, 3, miniply::PLYPropertyType::Float, pos);
            pcl::PointCloud<pcl::PointXYZRGB>::Ptr importedCloud(new pcl::PointCloud<pcl::PointXYZRGB>);
            for(uint32_t i = 0; i < numVerts; ++i) {
                aPoints->AddVertex(gp_Pnt(pos[i * 3], pos[i * 3 + 1], pos[i * 3 + 2]));
                pcl::PointXYZRGB point;
                point.x = pos[i * 3];
                point.y = pos[i * 3 + 1];
                point.z = pos[i * 3 + 2];
                importedCloud->push_back(point);
            }
            tempVec2.push_back(importedCloud);
        }
        aPointCloud->SetPoints(aPoints);
        aPointCloud->SetColor((Quantity_NameOfColor)(Quantity_NOC_BLUE + i));
        i += 10;
        d->m_pContext->SetAutomaticHilight(Standard_True);
        d->m_pContext->Display(aPointCloud, Standard_True);
        tempVec.push_back(aPointCloud);
    }
    if(d->m_measureCloud.size() <= index) {
        d->m_measureCloud.push_back(tempVec);
        PCLModule::m_measureClouds.push_back(tempVec2);
    } else {
        for(Handle_AIS_PointCloud item : d->m_measureCloud.at(index))
            d->m_pContext->Erase(item, Standard_True);
        d->m_measureCloud.replace(index, tempVec);
        PCLModule::m_measureClouds.replace(index, tempVec2);
    }
    d->m_pView->FitAll();
    d->m_pView->ZFitAll();
    d->m_pView->MustBeResized();
}

void OcctWidget::registPly(bool isCoarse)
{
    pcl::PointCloud<pcl::PointXYZRGB>::Ptr cloud = PCLModule::CoarseRegist(0);
    Handle(AIS_PointCloud) aPointCloud = new AIS_PointCloud();
    Handle(Graphic3d_ArrayOfPoints) aPoints = new Graphic3d_ArrayOfPoints(cloud->size());
    QVector<gp_Pnt> pV1, pV2;
    for (size_t i = 0; i < cloud->size(); ++i) {
        pcl::PointXYZRGB point = cloud->points[i];
        aPoints->AddVertex(gp_Pnt(point.x, point.y, point.z));
    }
    aPointCloud->SetPoints(aPoints);
    aPointCloud->SetColor((Quantity_NameOfColor)(Quantity_NOC_GREEN));
    d->m_pContext->SetAutomaticHilight(Standard_True);
    d->m_pContext->Display(aPointCloud, Standard_True);
}

void OcctWidget::removePlys(int index)
{
    d->m_measureCloud.remove(index);
    d->m_measureCloud.remove(index);
}
